package samples;

import its.model.environment.Environment;
import its.model.environment.crossroad.Crossroad;
import its.model.environment.crossroad.Program;
import its.opt.OptSimConf;
import its.opt.StatsLoader;
import its.opt.program.Evaluator;
import its.opt.program.ProgramBuilder;
import its.opt.structures.BOAParams;
import its.opt.utils.EvaluationFunctionFactory;
import its.opt.utils.LoggingUtils;
import its.opt.utils.OptimizationEvaluatorUtils;
import its.sim.Simulator;
import its.sim.SimulatorConfiguration;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import samples.objectivefunctions.ProgramTrafficAmount;
import engine.BoaAlgorithm;
import engine.BoaPopulation;
import engine.CachedObjectiveFunction;
import engine.PopulationEvaluator;
import engine.SingleThreadedEvaluator;
import engine.exitcriteria.AllIndividualsAreSimilar;
import engine.exitcriteria.MaxIterations;
import engine.exitcriteria.NoImprovementTerminationCondition;
import engine.individuals.ProgramedTrafficNetIndividual;
import engine.operators.BOAOffspringGeneratorOperator;
import engine.operators.BoaReplaceWorstOperator;
import engine.operators.ChartReportingMinAvgMaxOperator;
import engine.operators.ReportStatisticsOperator;
import engine.operators.TournamentSelectionOperator;
import engine.operators.binary.TrafficMutation;
import engine.utils.ListUtils;

public class ItsBoa {

	private static final Logger LOGGER = Logger.getLogger(ItsBoa.class.getName());
	
	/** Number of iterations. */
	private static int MAX_ITER = 100;

	/** Size of the population. */
	private static int POPULATION_SIZE = 40;
	
	private static SimulatorConfiguration conf;

	@SuppressWarnings("unchecked")
	public static void main(String[] args) throws Exception {
		
		Date d1 = new Date();
		
		conf = new SimulatorConfiguration(args);

		if (LOGGER.isLoggable(Level.INFO)) {
			LOGGER.info(LoggingUtils.makeLogStringForAlgorithmParameters(args, conf));
		}
		
		StatsLoader init_loader = null;
		try {
			init_loader = runSimulation();
		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, "Error during simulation run", e);
			throw new IllegalStateException(e);
		}
    
    
		Evaluator evaluator = EvaluationFunctionFactory.getEvaluator(args, conf,
				init_loader);
		
		BOAParams boaParams = prepareParams();

		
		List<ProgramedTrafficNetIndividual> listOfRandomBinaryIndividuals = generateListOfRandomProgramsNetIndividuals(boaParams);
		
		if (LOGGER.isLoggable(Level.INFO)) {
			LOGGER.info(LoggingUtils.makeLogStringForAlgorithmParameters(boaParams));
		}
		
		BoaPopulation<ProgramedTrafficNetIndividual> population = new BoaPopulation<ProgramedTrafficNetIndividual>(
				listOfRandomBinaryIndividuals, boaParams.n);

		BoaAlgorithm<ProgramedTrafficNetIndividual> alg = new BoaAlgorithm<ProgramedTrafficNetIndividual>(
				population, boaParams.n);

		ProgramTrafficAmount programTrafficAmount = new ProgramTrafficAmount(evaluator);
		
		CachedObjectiveFunction<ProgramedTrafficNetIndividual> objectiveFunctionWrapper = new CachedObjectiveFunction<ProgramedTrafficNetIndividual>(
				programTrafficAmount);
		
		alg.addExitPoint(
				new MaxIterations<ProgramedTrafficNetIndividual>(boaParams.maxGenerations));
		alg.addExitPoint(
				new AllIndividualsAreSimilar<ProgramedTrafficNetIndividual>(objectiveFunctionWrapper));
		alg.addExitPoint(
				new NoImprovementTerminationCondition<ProgramedTrafficNetIndividual>(
						objectiveFunctionWrapper));
		
		PopulationEvaluator<ProgramedTrafficNetIndividual> objectiveFunctions = 
			buildObjectiveFunctions(objectiveFunctionWrapper);
		
		alg.addOperator(
//				new RouletteReproduction<ProgramedTrafficNetIndividual>(objectiveFunctionWrapper));
				new TournamentSelectionOperator<ProgramedTrafficNetIndividual>(
						objectiveFunctionWrapper, boaParams));
		
		alg.addOperator(new BOAOffspringGeneratorOperator<ProgramedTrafficNetIndividual>(
				boaParams));
		
		alg.addOperator(new TrafficMutation(boaParams.mutationProbability));
		
		alg.addEvaluationPoint(objectiveFunctions);
		
		alg.addOperator(new BoaReplaceWorstOperator<ProgramedTrafficNetIndividual>(
				objectiveFunctionWrapper));
		
		ReportStatisticsOperator reporter = new ReportStatisticsOperator(objectiveFunctionWrapper);
		alg.addOperator(reporter);
		
		ChartReportingMinAvgMaxOperator<ProgramedTrafficNetIndividual> 
		chartReportingOperator = 
			new ChartReportingMinAvgMaxOperator<ProgramedTrafficNetIndividual>(
					"boa",
					conf.getConfigFile().getName(),
					objectiveFunctionWrapper);
		
		alg.addOperator(chartReportingOperator);
		
		objectiveFunctions.apply(population);
		reporter.apply(population);
		chartReportingOperator.apply(population);
		alg.run();

		objectiveFunctions.apply(alg.getPopulation());
		
		chartReportingOperator.drawStatistics();
		
		
		LOGGER.info("Good rated individuals: " + programTrafficAmount.getGoodIndividuals());
		Date d2 = new Date();
		LOGGER.info(new Long((d2.getTime() - d1 .getTime()) / 1000L).toString());
		
		// save best individual
		OptimizationEvaluatorUtils.saveBestIndividual(args, alg,
				objectiveFunctionWrapper);
		
	}

	/**
	 * Creates a list of objective functions (one in this case) to be optimized.
	 * 
	 * @param objectiveFunctionWrapper
	 *          A function that the list is based on.
	 * @return List of objective functions.
	 */
	@SuppressWarnings("unchecked")
	private static PopulationEvaluator<ProgramedTrafficNetIndividual> buildObjectiveFunctions(
			CachedObjectiveFunction<ProgramedTrafficNetIndividual> objectiveFunctionWrapper) {
		
		return new SingleThreadedEvaluator<ProgramedTrafficNetIndividual>(ListUtils
				.buildList(objectiveFunctionWrapper));
	}
	
	private static BOAParams prepareParams() {

		BOAParams params = new BOAParams();

		String populationSize = System.getProperty("population.size");
		if (populationSize != null && populationSize.trim().length() > 0) {
			POPULATION_SIZE = Integer.parseInt(populationSize);
		}
		String maxGenerations = System.getProperty("max.generations");
		if (maxGenerations != null && maxGenerations.trim().length() > 0) {
			MAX_ITER = Integer.parseInt(maxGenerations);
		}
		int percentOffspring = 50;
		String percentOffspringStr = System.getProperty("percent.offspring");
		if (percentOffspringStr != null && percentOffspringStr.trim().length() > 0) {
			percentOffspring = Integer.parseInt(percentOffspringStr);
		}
		double mutationProb = 0.3d;
		String mutationProbStr = System.getProperty("mutation.probability");
		if (mutationProbStr != null && mutationProbStr.trim().length() > 0) {
			mutationProb = Double.parseDouble(mutationProbStr);
		}
		int tournamentSize = 2;
		String tournamentSizeStr = System.getProperty("tournament.size");
		if (tournamentSizeStr != null && tournamentSizeStr.trim().length() > 0) {
			tournamentSize = Integer.parseInt(tournamentSizeStr);
		}
		int maxParents = 20;
		String maxParentsStr = System.getProperty("max.parents");
		if (maxParentsStr != null && maxParentsStr.trim().length() > 0) {
			maxParents = Integer.parseInt(maxParentsStr);
		}
		
		params.N = POPULATION_SIZE;
		// defined elsewhere
//		params.n = VECTOR_SIZE;
		params.percentOffspring = percentOffspring;
		params.tournamentSize = tournamentSize;
//		params.replacementCompetitorsCount = (POPULATION_SIZE/20 < VECTOR_SIZE) ? POPULATION_SIZE/20 : VECTOR_SIZE;
		// Unused in production mode
//		params.randSeed = 123456;
		params.allowMerge = false;
		params.maxGenerations = MAX_ITER;
		params.epsilon = 0.01f;
		params.maxIncoming = maxParents;
		params.mutationProbability = mutationProb;

		return params;
	}

	/**
	 * Generates initial population with random binary individuals.
	 * 
	 * @param boaParams
	 * 
	 * @return List of binary individuals that will form the basis for first
	 *         iteration of the algorithm.
	 */
	private static List<ProgramedTrafficNetIndividual> generateListOfRandomProgramsNetIndividuals(
			BOAParams boaParams) {

		List<ProgramedTrafficNetIndividual> result = new ArrayList<ProgramedTrafficNetIndividual>(boaParams.N);

		Collection<Crossroad> crossRoads = Environment.getActiveEnvironment()
				.getAllCrossroads();

		for (int i = 0; i < boaParams.N; i++) {
			List<Program> res = new ArrayList<Program>(crossRoads.size());
			for (Crossroad crossroad : crossRoads) {
				ProgramBuilder programBuilder = new ProgramBuilder(crossroad.getID());
				Program randomProgram = programBuilder.getProgramWithRandomizedPhasesDurations();
				res.add(randomProgram);
			}

			// we dont want to move that unnecessarily
			// since there is lot of other stuff to
			// prarametrize
			res.get(res.size()-1).setPhaseShift(0L);

			
			// ------------------------------------- fixed solutions ------------
			// ---- optimal for Model-1-200-1
//			res.get(0).getPhase(0).setDuration(4000L);
//			res.get(0).getPhase(1).setDuration(4000L);
//			res.get(0).getPhase(2).setDuration(4000L);
//			res.get(0).getPhase(3).setDuration(67000L);
			// ---- end of optimal for Model-1-200-1
			// ---- optimal for grabiszynska-2-200-1
//			res.get(0).setPhaseShift(i * 1000L); // {4-14,22-70}000 opt
//			res.get(0).getPhase(0).setDuration(67000L);
//			res.get(0).getPhase(1).setDuration(4000L);
//			res.get(0).getPhase(2).setDuration(4000L);
//			res.get(0).getPhase(3).setDuration(4000L);
//			res.get(1).getPhase(0).setDuration(67000L);
//			res.get(1).getPhase(1).setDuration(4000L);
//			res.get(1).getPhase(2).setDuration(4000L);
//			res.get(1).getPhase(3).setDuration(4000L);
			// ---- end of optimal for grabiszynska-2-200-1
			// ---- optimal for hallera-6-200-1
//			res.get(0).setPhaseShift(RandomFactory.getNextInt(4)* 19000L); // 0
//			res.get(0).getPhase(0).setDuration(4000L);
//			res.get(0).getPhase(1).setDuration(67000L);
//			res.get(0).getPhase(2).setDuration(4000L);
//			res.get(0).getPhase(3).setDuration(4000L);
//			res.get(1).setPhaseShift(RandomFactory.getNextInt(4)* 19000L); // 0
//			res.get(1).getPhase(0).setDuration(4000L);
//			res.get(1).getPhase(1).setDuration(67000L);
//			res.get(1).getPhase(2).setDuration(4000L);
//			res.get(1).getPhase(3).setDuration(4000L);
//			res.get(2).setPhaseShift(RandomFactory.getNextInt(4)* 19000L); // 38000
//			res.get(2).getPhase(0).setDuration(67000L);
//			res.get(2).getPhase(1).setDuration(4000L);
//			res.get(2).getPhase(2).setDuration(4000L);
//			res.get(3).setPhaseShift(RandomFactory.getNextInt(4)* 19000L); // 38000
//			res.get(3).getPhase(0).setDuration(67000L);
//			res.get(3).getPhase(1).setDuration(4000L);
//			res.get(3).getPhase(2).setDuration(4000L);
//			res.get(3).getPhase(3).setDuration(4000L);
//			res.get(4).setPhaseShift(RandomFactory.getNextInt(4)* 19000L); // 57000
//			res.get(4).getPhase(0).setDuration(67000L);
//			res.get(4).getPhase(1).setDuration(4000L);
//			res.get(4).getPhase(2).setDuration(4000L);
//			res.get(5).getPhase(0).setDuration(4000L);
//			res.get(5).getPhase(1).setDuration(67000L);
//			res.get(5).getPhase(2).setDuration(4000L);
//			res.get(5).getPhase(3).setDuration(4000L);
			// ---- end of optimal for hallera-6-200-1
			// ---- optimal for grunwald-2-200-8
//			res.get(0).setPhaseShift(i * 1000L); // 31000
//			res.get(0).getPhase(0).setDuration(34000L);
//			res.get(0).getPhase(1).setDuration(37000L);
//			res.get(0).getPhase(2).setDuration(4000L);
//			res.get(1).getPhase(0).setDuration(33000L);
//			res.get(1).getPhase(1).setDuration(36000L);
//			res.get(1).getPhase(2).setDuration(5000L);
			// ---- end of optimal for grunwald-2-200-8
			// ------------------------------------- end fixed solutions ------------
			
			result.add(new ProgramedTrafficNetIndividual(res.toArray(new Program[res.size()])));
			
		}
		
		boaParams.n = result.get(0).size();
		
		return result;
	}
	
	public static StatsLoader runSimulation() throws Exception {
		final SimulatorConfiguration opt_conf = new OptSimConf(conf);

		final Simulator sim = new Simulator(opt_conf);
		LOGGER.info("Running simulation, please wait");
		sim.run();
		return new StatsLoader(sim);
	}
	
}
